/*
 *  Copyright (c) 2019-2020, somewhere (somewhere0813@gmail.com).
 *  <p>
 *  Licensed under the GNU Lesser General Public License 3.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *  <p>
 * https://www.gnu.org/licenses/lgpl.html
 *  <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.shanhesoft.java.modules.tool.service.impl;

import com.shanhesoft.java.common.core.constant.CacheNameConstants;
import com.shanhesoft.java.common.persistence.service.impl.BaseServiceImpl;
import com.shanhesoft.java.common.util.PathUtils;
import com.shanhesoft.java.common.util.PropertiesUtils;
import com.shanhesoft.java.modules.tool.domain.vo.Chunk;
import com.shanhesoft.java.modules.tool.repository.ChunkRepository;
import com.shanhesoft.java.modules.tool.service.FileService;

import lombok.AllArgsConstructor;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.Map;

/**
 * @author somewhere
 * @since 2019/2/1
 */
@Service
@CacheConfig(cacheNames = CacheNameConstants.EMAIL_DETAILS)
@AllArgsConstructor
public class FileServiceImpl extends BaseServiceImpl<ChunkRepository, Chunk> implements FileService {
	@Override
	public Map<String, String> getFileList() {
		return PropertiesUtils.getFileList();
	}

	@Override
	public void fileDownload(String fileName, HttpServletRequest request, HttpServletResponse response) {
		//得到文件信息
		String fullPath = PropertiesUtils.getFilePath(fileName);
		File downloadFile = new File(fullPath);

		// 设置响应内容类型
		response.setContentType(PropertiesUtils.getContentType(fullPath));

		// 响应头的文件信息
		String headerValue = null;
		try {
			//防止中文乱码！
			headerValue = String.format("attachment; filename=\"%s\"", java.net.URLEncoder.encode(downloadFile.getName(), "UTF-8"));
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		response.setHeader("Content-Disposition", headerValue);
		// 解析断点续传相关信息
		response.setHeader("Accept-Ranges", "bytes");
		long downloadSize = downloadFile.length();
		//开始位置，结束位置(0-0指全部)
		long fromPos = 0, toPos = 0;
		if (request.getHeader("Range") == null) {
			//设置文件总大小
			response.setHeader("Content-Length", downloadSize+"");
		} else {
			// 若客户端传来Range，说明之前下载了一部分，设置206状态(SC_PARTIAL_CONTENT)
			response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
			String range = request.getHeader("Range");
			String bytes = range.replaceAll("bytes=", "");
			String[] ary = bytes.split("-");
			fromPos = Long.parseLong(ary[0]);
			if (ary.length == 2) {
				toPos = Long.parseLong(ary[1]);
			}
			int size;
			if (toPos > fromPos) {
				size = (int) (toPos - fromPos);
			} else {
				size = (int) (downloadSize - fromPos);
			}
			response.setHeader("Content-Length", String.valueOf(size));
			downloadSize = size;
		}
		try(
			RandomAccessFile in=new RandomAccessFile(downloadFile, "rw");
			OutputStream out = response.getOutputStream()
		) {
			// 设置下载起始位置
			if (fromPos > 0) {
				in.seek(fromPos);
			}
			// 缓冲区大小
			int bufLen = (int) (downloadSize < 2048 ? downloadSize : 2048);
			byte[] buffer = new byte[bufLen];
			int num;
			// 当前写到客户端的大小
			int count = 0;
			while ((num = in.read(buffer)) >0) {
				out.write(buffer, 0, num);
				count += num;
				//处理最后一段，计算不满缓冲区的大小
				if (downloadSize - count < bufLen) {
					bufLen = (int) (downloadSize-count);
					if(bufLen==0){
						break;
					}
					buffer = new byte[bufLen];
				}
			}
			response.flushBuffer();
		} catch (IOException e) {
			System.out.println("用户停止了下载!");
		}
	}

	@Override
	public String fileUploadPost(Chunk chunk, HttpServletResponse response) {
		/**
		 * 每一个上传块都会包含如下分块信息：
		 * chunkNumber: 当前块的次序，第一个块是 1，注意不是从 0 开始的。
		 * totalChunks: 文件被分成块的总数。
		 * chunkSize: 分块大小，根据 totalSize 和这个值你就可以计算出总共的块数。注意最后一块的大小可能会比这个要大。
		 * currentChunkSize: 当前块的大小，实际大小。
		 * totalSize: 文件总大小。
		 * identifier: 这个就是每个文件的唯一标示。
		 * filename: 文件名。
		 * relativePath: 文件夹上传的时候文件的相对路径属性。
		 * 一个分块可以被上传多次，当然这肯定不是标准行为，但是在实际上传过程中是可能发生这种事情的，这种重传也是本库的特性之一。
		 *
		 * 根据响应码认为成功或失败的：
		 * 200 文件上传完成
		 * 201 文加快上传成功
		 * 500 第一块上传失败，取消整个文件上传
		 * 507 服务器出错自动重试该文件块上传
		 */
		File file= new File(PathUtils.getFileDir(), chunk.getFilename());
		//第一个块,则新建文件
		if(chunk.getChunkNumber()==1 && !file.exists()){
			try {
				file.createNewFile();
			} catch (IOException e) {
				response.setStatus(500);
				return "exception:createFileException";
			}
		}

		//进行写文件操作
		try(
			//将块文件写入文件中
			InputStream fos=chunk.getFile().getInputStream();
			RandomAccessFile raf =new RandomAccessFile(file,"rw")
		) {
			int len=-1;
			byte[] buffer=new byte[1024];
			raf.seek((chunk.getChunkNumber()-1)*1024*1024);
			while((len=fos.read(buffer))!=-1){
				raf.write(buffer,0,len);
			}
		} catch (IOException e) {
			e.printStackTrace();
			if(chunk.getChunkNumber()==1) {
				file.delete();
			}
			response.setStatus(507);
			return "exception:writeFileException";
		}
		if(chunk.getChunkNumber().equals(chunk.getTotalChunks())){
			response.setStatus(200);
			if(!"".equals(chunk.getFilename())){
				PropertiesUtils.setProperty(chunk.getFilename(),PathUtils.getFileDir()+ "/" + chunk.getFilename());
			}
			return "over";
		}else {
			response.setStatus(201);
			return "ok";
		}
	}

	@Override
	public void fileUploadGet(Chunk chunk, HttpServletResponse response) {
		//存在响应200（浏览器直接提示上传成功），不存在响应304（浏览器将发送POST请求）
		if (PropertiesUtils.contains(chunk.getFilename())) {
			//已上传过，秒传
			response.setStatus(200);
		}else{
			response.setStatus(304);
		}
	}

}
